#if SYSTEM_DRAWING

using System.Drawing;
using System.Drawing.Drawing2D;
using static QRCoder.ArtQRCode;
using static QRCoder.QRCodeGenerator;

// pull request raised to extend library used. 
namespace QRCoder;

/// <summary>
/// Represents an art-style QR code generator that provides functionality to render QR codes with dots as modules.
/// </summary>
#if NET6_0_OR_GREATER
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
public class ArtQRCode : AbstractQRCode, IDisposable
{
    /// <summary>
    /// Initializes a new instance of the <see cref="ArtQRCode"/> class.
    /// Constructor without params to be used in COM Objects connections
    /// </summary>
    public ArtQRCode() { }

    /// <summary>
    /// Initializes a new instance of the <see cref="ArtQRCode"/> class with the specified <see cref="QRCodeData"/>.
    /// </summary>
    /// <param name="data"><see cref="QRCodeData"/> generated by the <see cref="QRCodeGenerator"/>.</param>
    public ArtQRCode(QRCodeData data) : base(data) { }

    /// <summary>
    /// Renders an art-style QR code with dots as modules. (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
    /// </summary>
    /// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
    /// <returns>QRCode graphic as bitmap</returns>
    public Bitmap GetGraphic(int pixelsPerModule)
        => GetGraphic(pixelsPerModule, Color.Black, Color.White, Color.Transparent);

    /// <summary>
    /// Renders an art-style QR code with dots as modules and a background image (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
    /// </summary>
    /// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
    /// <returns>QRCode graphic as bitmap</returns>
    public Bitmap GetGraphic(Bitmap? backgroundImage = null)
        => GetGraphic(10, Color.Black, Color.White, Color.Transparent, backgroundImage: backgroundImage);

    /// <summary>
    /// Renders an art-style QR code with dots as modules and various user settings
    /// </summary>
    /// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
    /// <param name="darkColor">Color of the dark modules</param>
    /// <param name="lightColor">Color of the light modules</param>
    /// <param name="backgroundColor">Color of the background</param>
    /// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
    /// <param name="pixelSizeFactor">Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be.</param>
    /// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
    /// <param name="quietZoneRenderingStyle">Style of the quiet zones</param>
    /// <param name="backgroundImageStyle">Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone</param>
    /// <param name="finderPatternImage">Optional image that should be used instead of the default finder patterns</param>
    /// <returns>QRCode graphic as bitmap</returns>
    public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, Bitmap? backgroundImage = null, double pixelSizeFactor = 1,
                             bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Dotted,
                             BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Bitmap? finderPatternImage = null)
    {
        if (pixelSizeFactor > 1)
            throw new ArgumentOutOfRangeException(nameof(pixelSizeFactor), "The parameter pixelSizeFactor must be between 0 and 1. (0-100%)");
        int pixelSize = (int)Math.Min(pixelsPerModule, Math.Floor(pixelsPerModule * pixelSizeFactor));

        var numModules = QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8);
        var offset = (drawQuietZones ? 0 : 4);
        var size = numModules * pixelsPerModule;

        var bitmap = new Bitmap(size, size);

        using (var graphics = Graphics.FromImage(bitmap))
        {
            using var lightBrush = new SolidBrush(lightColor);
            using var darkBrush = new SolidBrush(darkColor);
            // make background transparent
            using (var brush = new SolidBrush(backgroundColor))
                graphics.FillRectangle(brush, new Rectangle(0, 0, size, size));
            //Render background if set
            if (backgroundImage != null)
            {
                if (backgroundImageStyle == BackgroundImageStyle.Fill)
                    graphics.DrawImage(Resize(backgroundImage, size), 0, 0);
                else if (backgroundImageStyle == BackgroundImageStyle.DataAreaOnly)
                {
                    var bgOffset = 4 - offset;
                    graphics.DrawImage(Resize(backgroundImage, size - (2 * bgOffset * pixelsPerModule)), 0 + (bgOffset * pixelsPerModule), (bgOffset * pixelsPerModule));
                }
            }


            var darkModulePixel = MakeDotPixel(pixelsPerModule, pixelSize, darkBrush);
            var lightModulePixel = MakeDotPixel(pixelsPerModule, pixelSize, lightBrush);

            for (var x = 0; x < numModules; x += 1)
            {
                for (var y = 0; y < numModules; y += 1)
                {
                    var rectangleF = new Rectangle(x * pixelsPerModule, y * pixelsPerModule, pixelsPerModule, pixelsPerModule);

                    var pixelIsDark = QrCodeData.ModuleMatrix[offset + y][offset + x];
                    var solidBrush = pixelIsDark ? darkBrush : lightBrush;
                    var pixelImage = pixelIsDark ? darkModulePixel : lightModulePixel;

                    if (!IsPartOfFinderPattern(x, y, numModules, offset))
                        if (drawQuietZones && quietZoneRenderingStyle == QuietZoneStyle.Flat && IsPartOfQuietZone(x, y, numModules))
                            graphics.FillRectangle(solidBrush, rectangleF);
                        else
                            graphics.DrawImage(pixelImage, rectangleF);
                    else if (finderPatternImage == null)
                        graphics.FillRectangle(solidBrush, rectangleF);
                }
            }
            if (finderPatternImage != null)
            {
                var finderPatternSize = 7 * pixelsPerModule;
                var finderPatternOffset = drawQuietZones ? 4 * pixelsPerModule : 0;
                graphics.DrawImage(finderPatternImage, new Rectangle(finderPatternOffset, finderPatternOffset, finderPatternSize, finderPatternSize));
                graphics.DrawImage(finderPatternImage, new Rectangle(size - finderPatternOffset - finderPatternSize, finderPatternOffset, finderPatternSize, finderPatternSize));
                graphics.DrawImage(finderPatternImage, new Rectangle(finderPatternOffset, size - finderPatternOffset - finderPatternSize, finderPatternSize, finderPatternSize));
            }
            graphics.Save();
        }
        return bitmap;
    }

    /// <summary>
    /// If the pixelSize is bigger than the pixelsPerModule or may end up filling the Module making a traditional QR code.
    /// </summary>
    /// <param name="pixelsPerModule">Pixels used per module rendered</param>
    /// <param name="pixelSize">Size of the dots</param>
    /// <param name="brush">Color of the pixels</param>
    /// <returns></returns>
    private static Bitmap MakeDotPixel(int pixelsPerModule, int pixelSize, SolidBrush brush)
    {
        // draw a dot
        var bitmap = new Bitmap(pixelSize, pixelSize);
        using (var graphics = Graphics.FromImage(bitmap))
        {
            graphics.FillEllipse(brush, new Rectangle(0, 0, pixelSize, pixelSize));
            graphics.Save();
        }

        var pixelWidth = Math.Min(pixelsPerModule, pixelSize);
        var margin = Math.Max((pixelsPerModule - pixelWidth) / 2, 0);

        // center the dot in the module and crop to stay the right size.
        var cropped = new Bitmap(pixelsPerModule, pixelsPerModule);
        using (var graphics = Graphics.FromImage(cropped))
        {
            graphics.DrawImage(bitmap, new Rectangle(margin, margin, pixelWidth, pixelWidth),
                new RectangleF(((float)pixelSize - pixelWidth) / 2, ((float)pixelSize - pixelWidth) / 2, pixelWidth, pixelWidth),
                GraphicsUnit.Pixel);
            graphics.Save();
        }

        return cropped;
    }


    /// <summary>
    /// Checks if a given module(-position) is part of the quietzone of a QR code
    /// </summary>
    /// <param name="x">X position</param>
    /// <param name="y">Y position</param>
    /// <param name="numModules">Total number of modules per row</param>
    /// <returns>true, if position is part of quiet zone</returns>
    private static bool IsPartOfQuietZone(int x, int y, int numModules)
    {
        return
            x < 4 || //left 
            y < 4 || //top
            x > numModules - 5 || //right
            y > numModules - 5; //bottom                
    }


    /// <summary>
    /// Checks if a given module(-position) is part of one of the three finder patterns of a QR code
    /// </summary>
    /// <param name="x">X position</param>
    /// <param name="y">Y position</param>
    /// <param name="numModules">Total number of modules per row</param>
    /// <param name="offset">Offset in modules (usually depending on drawQuietZones parameter)</param>
    /// <returns>true, if position is part of any finder pattern</returns>
    private static bool IsPartOfFinderPattern(int x, int y, int numModules, int offset)
    {
        var cornerSize = 11 - offset;
        var outerLimitLow = (numModules - cornerSize - 1);
        var outerLimitHigh = outerLimitLow + 8;
        var invertedOffset = 4 - offset;
        return
            (x >= invertedOffset && x < cornerSize && y >= invertedOffset && y < cornerSize) || //Top-left finder pattern
            (x > outerLimitLow && x < outerLimitHigh && y >= invertedOffset && y < cornerSize) || //Top-right finder pattern
            (x >= invertedOffset && x < cornerSize && y > outerLimitLow && y < outerLimitHigh); //Bottom-left finder pattern
    }

    /// <summary>
    /// Resize to a square bitmap, but maintain the aspect ratio by padding transparently.
    /// </summary>
    /// <param name="image"></param>
    /// <param name="newSize"></param>
    /// <returns>Resized image as bitmap</returns>
    private static Bitmap Resize(Bitmap image, int newSize)
    {
        float scale = Math.Min((float)newSize / image.Width, (float)newSize / image.Height);
        var scaledWidth = (int)(image.Width * scale);
        var scaledHeight = (int)(image.Height * scale);
        var offsetX = (newSize - scaledWidth) / 2;
        var offsetY = (newSize - scaledHeight) / 2;

        var scaledImage = new Bitmap(image, new Size(scaledWidth, scaledHeight));

        var bm = new Bitmap(newSize, newSize);

        using (var graphics = Graphics.FromImage(bm))
        {
            using var brush = new SolidBrush(Color.Transparent);
            graphics.FillRectangle(brush, new Rectangle(0, 0, newSize, newSize));

            graphics.InterpolationMode = InterpolationMode.High;
            graphics.CompositingQuality = CompositingQuality.HighQuality;
            graphics.SmoothingMode = SmoothingMode.AntiAlias;

            graphics.DrawImage(scaledImage, new Rectangle(offsetX, offsetY, scaledWidth, scaledHeight));
        }
        return bm;
    }

    /// <summary>
    /// Defines how the quiet zones shall be rendered.
    /// </summary>
    public enum QuietZoneStyle
    {
        /// <summary>
        /// Quiet zones are rendered with dotted modules.
        /// </summary>
        Dotted,

        /// <summary>
        /// Quiet zones are rendered with flat, solid modules.
        /// </summary>
        Flat
    }

    /// <summary>
    /// Defines how the background image (if set) shall be rendered.
    /// </summary>
    public enum BackgroundImageStyle
    {
        /// <summary>
        /// Background image spans the complete graphic.
        /// </summary>
        Fill,

        /// <summary>
        /// Background image is only painted in the data area, excluding the quiet zone.
        /// </summary>
        DataAreaOnly
    }
}

/// <summary>
/// Provides static methods for creating art-style QR codes.
/// </summary>
#if NET6_0_OR_GREATER
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
public static class ArtQRCodeHelper
{
    /// <summary>
    /// Helper function to create an ArtQRCode graphic with a single function call
    /// </summary>
    /// <param name="plainText">Text/payload to be encoded inside the QR code</param>
    /// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
    /// <param name="darkColor">Color of the dark modules</param>
    /// <param name="lightColor">Color of the light modules</param>
    /// <param name="backgroundColor">Color of the background</param>
    /// <param name="eccLevel">The level of error correction data</param>
    /// <param name="forceUtf8">Shall the generator be forced to work in UTF-8 mode?</param>
    /// <param name="utf8BOM">Should the byte-order-mark be used?</param>
    /// <param name="eciMode">Which ECI mode shall be used?</param>
    /// <param name="requestedVersion">Set fixed QR code target version.</param>
    /// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
    /// <param name="pixelSizeFactor">Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be.</param>
    /// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
    /// <param name="quietZoneRenderingStyle">Style of the quiet zones</param>
    /// <param name="backgroundImageStyle">Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone</param>
    /// <param name="finderPatternImage">Optional image that should be used instead of the default finder patterns</param>
    /// <returns>QRCode graphic as bitmap</returns>
    public static Bitmap GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, ECCLevel eccLevel, bool forceUtf8 = false,
                                   bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Bitmap? backgroundImage = null, double pixelSizeFactor = 1.0,
                                   bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Flat,
                                   BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Bitmap? finderPatternImage = null)
    {
        using var qrGenerator = new QRCodeGenerator();
        using var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion);
        using var qrCode = new ArtQRCode(qrCodeData);
        return qrCode.GetGraphic(pixelsPerModule, darkColor, lightColor, backgroundColor, backgroundImage, pixelSizeFactor, drawQuietZones, quietZoneRenderingStyle, backgroundImageStyle, finderPatternImage);
    }
}

#endif
